/// <reference path = "../shader/ShaderComponent.ts" />

module lcc$render {

const {ccclass, property, menu } = cc._decorator;

//@ts-ignore
let gfx = cc.gfx;

/**
 * 顶点属性
 */
export interface VertexAttribute {
	name : string;
	type : number;
	num : number;
	normalize ?: boolean;
};

/**
 * 着色器变量类型
 */
export enum ShaderVariableType {
	/**
	 * 顶点属性
	 */
	ATTRIBUTE,

	/**
	 * 一致变量
	 */
	UNIFORM,
};

// 位置
const ATTR_POSITION = { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 };

/**
 * 形状类型
 */
enum ShapeType {
	/**
	 * 着色器形状
	 */
	SHADER,

	/**
	 * 节点形状
	 */
	NODE,
}

@ccclass("lcc$render.RenderSystem")
@menu("i18n:lcc-render.menu_component/RenderSystem")
export class RenderSystem extends cc.RenderComponent {

	@property({
		editorOnly : true,
		serializable : false,
		readonly : true,
		tooltip : "统计属性所使用的空间大小"
	})
	attrsSize:number = 0;
	
    /**
     * 材质污染
     */
	_matsDirty:boolean = true;
	
	/**
	 * 形状污染
	 */
	_shapeDirty:boolean = true;

    /**
     * 属性污染
     */
    _attrsDirty:boolean = true;

	/**
	 * 顶点格式
	 */
	_vFormat:any = null;

	/**
	 * 顶点属性
	 */
	_vAttributes:VertexAttribute[] = null;
	
	/**
	 * 形状类型
	 */
	_shapeType:ShapeType = ShapeType.NODE;

	/**
	 * 顶点数量
	 */
	_verticesCount:number = 0;

	/**
	 * 索引数量
	 */
	_indicesCount:number = 0;

	/**
	 * 节点矩形区域
	 */
	_localRect: number[] = null;
	
    onEnable(){
        super.onEnable();
        // @ts-ignore
        this.node.on(cc.Node.EventType.SIZE_CHANGED, this.onNodeSizeChanged, this);
        this.node.on("shader_update_material", this.setMatsDirty, this);
        this.node.on("shader_update_attribute", this.setAttrsDirty, this);
        this.node.on("shader_update_shape", this.checkRenderShape, this);
		this.node.on("shader_update_vertex", this.setVertsDirty, this);
		this.setDirty();
    }

    onDisable(){
		super.onDisable();
		this.node.targetOff(this);
	}

	/**
	 * 设置材质污染
	 */
	setMatsDirty(){
		this._matsDirty = true;
		this.setVertsDirty();
	}

	/**
	 * 设置属性污染
	 */
	setAttrsDirty(){
		//Editor.log("setAttrsDirty");
		this._attrsDirty = true;
		this.setShapeDirty();
	}

	/**
	 * 设置形状污染
	 */
	setShapeDirty(){
		//Editor.log("setShapeDirty");
		this._shapeDirty = true;
		this.setVertsDirty();
	}

	/**
	 * 检查渲染形状
	 */
	private checkRenderShape(){
		this.setShapeDirty();

		this._shapeType = ShapeType.NODE;

		this.node.emit("render_check_shape", this);

		if(this._shapeType == ShapeType.NODE){
			this._verticesCount = 4;
			this._indicesCount = 6;
		}

		this.node.emit("render_shape_checked", this);
	}
	
	/**
	 * 设置着色器形状
	 * @param verticesCount 
	 * @param indicesCount 
	 */
	setShaderShape(verticesCount:number, indicesCount:number){
		this._shapeType = ShapeType.SHADER;
		this._verticesCount = verticesCount;
		this._indicesCount = indicesCount;
	}

	/**
	 * 当节点尺寸变化
	 */
	private onNodeSizeChanged(){
		if(this._shapeType === ShapeType.NODE){
			this.setVertsDirty();
		}
	}

	/**
	 * 设置顶点污染
	 */
	setVertsDirty(){
		// @ts-ignore
        super.setVertsDirty();
	}
	
	/**
	 * 设置所有污染
	 */
	setDirty(){
		this.checkRenderShape();
		this._matsDirty = true;
		this._attrsDirty = true;
		this._shapeDirty = true;
		// @ts-ignore
		this.setVertsDirty();
	}
	
	/**
	 * 更新为节点形状
	 */
	protected updateToNodeShape(){
		//Editor.log("updateToNodeShape");
		
		// @ts-ignore
		let rdata = this._assembler.renderData;
		rdata.initQuadIndices(rdata.iDatas[0]);

        this._localRect = [ 0, 0, 0, 0 ];
	}

	/**
	 * 更新节点顶点
	 */
	protected updateNodeVertex(){
		let node = this.node,
            cw = node.width, ch = node.height,
            appx = node.anchorX * cw, appy = node.anchorY * ch,
            l, b, r, t;
        l = -appx;
        b = -appy;
        r = cw - appx;
        t = ch - appy;
        let local = this._localRect;
        local[0] = l;
        local[1] = b;
        local[2] = r;
		local[3] = t;
		
		this.updateWorldVerts();
	}
	
	/**
	 * 更新渲染数据
	 */
	updateRenderData(){
		this.prepareRender();
		this.updateMaterial();
		this.updateAttribute();
		this.updateShape();
		this.updateVertex();
	}

	/**
	 * 准备渲染
	 */
	protected prepareRender(){

		this.node.emit("render_prepare", this);

	}

	/**
	 * 更新材质
	 */
	protected updateMaterial(){
		if(this._matsDirty){
			//Editor.log("updateMaterial");
			
            this.node.emit("render_update_material", this);

			this._updateBlendFunc(false);
			
            this._matsDirty = false;
        }
	}

	/**
	 * 检查顶点属性名
	 * @param name 
	 */
	checkVertexAttributeName(name:string){
		for(let va of this._vAttributes){
			if(va.name == name){
				return false;
			}
		}
		return true;
	}

	/**
	 * 添加顶点
	 * @param vattr 属性对象
	 * @returns 数据偏移
	 */
	addVertexAttribute(vattr:VertexAttribute, floatsize:number):number{
        let offest = -1;
        // @ts-ignore
        let assembler = this._assembler;
		if(this.checkVertexAttributeName(vattr.name)){
			offest = assembler.floatsPerVert;
			this._vAttributes.push(vattr);
			assembler.floatsPerVert += floatsize;
			if(CC_EDITOR){
				this.attrsSize = assembler.floatsPerVert * this._verticesCount;
			}
		}else{
            if(CC_EDITOR){
                Editor.error("attribute name conflict " + vattr.name);
            }else{
				cc.error("attribute name conflict " + vattr.name);
			}
        }
		return offest;
	}

	/**
	 * 获得顶点格式
	 */
    getVfmt() {
		if(!this._vFormat){
			this._vFormat = new gfx.VertexFormat(this._vAttributes);
		}
        return this._vFormat;
    }
	
	/**
	 * 更新属性
	 */
	protected updateAttribute(){
		if(this._attrsDirty){
            //Editor.log("_updateAttribute");

            // @ts-ignore
            let assembler = this._assembler;

			this._vFormat = null;
			this._vAttributes = [ ATTR_POSITION ];
			assembler.floatsPerVert = 2;
			if(CC_EDITOR){
				this.attrsSize = assembler.floatsPerVert * this._verticesCount;
			}
			
            this.node.emit("render_update_attribute", this);

            this._attrsDirty = false;
        }
	}
    
	/**
	 * 更新形状
	 */
	protected updateShape(){
		if(this._shapeDirty){
			//Editor.log("updateShape"); 

			// @ts-ignore
			let assembler = this._assembler;

			assembler.verticesCount = this._verticesCount;
			assembler.indicesCount = this._indicesCount;

			assembler.resetRenderData();

			if(this._shapeType === ShapeType.NODE){
				this.updateToNodeShape();
			}else{
				this.node.emit("render_update_shape", this);
			}

			this._shapeDirty = false;
		}
	}

	/**
	 * 更新顶点
	 */
	protected updateVertex(){
		// @ts-ignore
		if(this._vertsDirty){
			//Editor.log("_updateRenderData");

			if(this._shapeType === ShapeType.NODE){
				this.updateNodeVertex();
			}

			this.node.emit("render_update_vertex", this);

			// @ts-ignore
			this._vertsDirty = false;
		}
	}
    
    updateWorldVerts() {
		if(this._shapeType === ShapeType.NODE){
			if (CC_NATIVERENDERER) {
				this.updateNodeWorldVertsNative();
			} else {
				this.updateNodeWorldVertsWebGL();
			}
		}else{
			this.node.emit("render_update_worldvertex", this);
		}
    }

    updateNodeWorldVertsWebGL() {
		// @ts-ignore
		let assembler = this._assembler;
        let local = this._localRect;
        let verts = assembler.renderData.vDatas[0];

		//@ts-ignore
        let matrix = this.node._worldMatrix;
        let matrixm = matrix.m,
            a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],
            tx = matrixm[12], ty = matrixm[13];

        let vl = local[0], vr = local[2],
            vb = local[1], vt = local[3];
        
        /*
        m00 = 1, m01 = 0, m02 = 0, m03 = 0,
        m04 = 0, m05 = 1, m06 = 0, m07 = 0,
        m08 = 0, m09 = 0, m10 = 1, m11 = 0,
        m12 = 0, m13 = 0, m14 = 0, m15 = 1
        */
        let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;

        let index = 0;
        let floatsPerVert = assembler.floatsPerVert;
        if (justTranslate) {
            // left bottom
            verts[index] = vl + tx;
            verts[index+1] = vb + ty;
            index += floatsPerVert;
            // right bottom
            verts[index] = vr + tx;
            verts[index+1] = vb + ty;
            index += floatsPerVert;
            // left top
            verts[index] = vl + tx;
            verts[index+1] = vt + ty;
            index += floatsPerVert;
            // right top
            verts[index] = vr + tx;
            verts[index+1] = vt + ty;
        } else {
            let al = a * vl, ar = a * vr,
            bl = b * vl, br = b * vr,
            cb = c * vb, ct = c * vt,
            db = d * vb, dt = d * vt;

            // left bottom
            // newx = vl * a + vb * c + tx
            // newy = vl * b + vb * d + ty
            verts[index] = al + cb + tx;
            verts[index+1] = bl + db + ty;
            index += floatsPerVert;
            // right bottom
            verts[index] = ar + cb + tx;
            verts[index+1] = br + db + ty;
            index += floatsPerVert;
            // left top
            verts[index] = al + ct + tx;
            verts[index+1] = bl + dt + ty;
            index += floatsPerVert;
            // right top
            verts[index] = ar + ct + tx;
            verts[index+1] = br + dt + ty;
        }
    }

    updateNodeWorldVertsNative() {
		// @ts-ignore
		let assembler = this._assembler;
        let local = this._localRect;
        let verts = assembler.renderData.vDatas[0];
        let floatsPerVert = assembler.floatsPerVert;
		
        let vl = local[0],
            vr = local[2],
            vb = local[1],
            vt = local[3];
      
        let index: number = 0;
        // left bottom
        verts[index] = vl;
        verts[index+1] = vb;
        index += floatsPerVert;
        // right bottom
        verts[index] = vr;
        verts[index+1] = vb;
        index += floatsPerVert;
        // left top
        verts[index] = vl;
        verts[index+1] = vt;
        index += floatsPerVert;
        // right top
        verts[index] = vr;
        verts[index+1] = vt;
    }

	//------------------------------------------- 着色器组件
	
	/**
	 * 获得着色器组件
	 * @param type 
	 * @param tag 
	 */
	protected getShaderComponent<T extends ShaderComponent>(type: {prototype: T}, tag?:string){
		if(tag === undefined){
			return this.getComponent(type);
		}else{
			for(let comp of this.getComponents(type)){
				if(comp._tag == tag){
					return comp;
				}
			}
		}
	}
	
	/**
	 * 获得着色器组件数组
	 * @param type 
	 * @param tag 
	 */
	protected getShaderComponents<T extends ShaderComponent>(type: {prototype: T}, tag?:string){
		if(tag === undefined){
			return this.getComponents(type);
		}else{
			let comps = this.getComponents(type);
			return comps.filter((a)=>{ a._tag == tag; });
		}
	}

	//------------------------------------------- 兼容 RenderComponent

	_activateMaterial(){
		// @ts-ignore
		super._activateMaterial();
		this.setDirty();
	}

	_updateMaterial () {
        this.updateMaterial();
	}

    _validateRender () {
        // @ts-ignore
        if(!this._materials[0]){
            // @ts-ignore
            return this.disableRender();
        }

        this.node.emit("render_validate_render", this);
	}
	
	//------------------------------------------- BlendFunc

	_srcBlendFactor: cc.macro.BlendFactor = cc.macro.BlendFactor.SRC_ALPHA;
	@property({
		animatable: false,
		type: cc.macro.BlendFactor,
		tooltip: CC_DEV && 'i18n:COMPONENT.sprite.src_blend_factor',
	})
	get srcBlendFactor(){
		return this._srcBlendFactor;
	}
	set srcBlendFactor(value:cc.macro.BlendFactor){
		if (this._srcBlendFactor === value) return;
		this._srcBlendFactor = value;
		this._updateBlendFunc(true);
		// @ts-ignore
		this._onBlendChanged && this._onBlendChanged();
	}

	_dstBlendFactor: cc.macro.BlendFactor =  cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA;
	@property({
		animatable: false,
        type: cc.macro.BlendFactor,
        tooltip: CC_DEV && 'i18n:COMPONENT.sprite.dst_blend_factor',
	})
	get dstBlendFactor(){
		return this._dstBlendFactor;
	}
	set dstBlendFactor(value:cc.macro.BlendFactor){
		if (this._dstBlendFactor === value) return;
		this._dstBlendFactor = value;
		this._updateBlendFunc(true);
	}

    setMaterial (index, material) {
		let materialVar = super.setMaterial(index, material);

		if (this._srcBlendFactor !== cc.macro.BlendFactor.SRC_ALPHA || 
			this._dstBlendFactor !== cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA) {
            this._updateMaterialBlendFunc(materialVar);
		}
		
		this.setMatsDirty();

        return materialVar;
    }

    _updateBlendFunc (force) {
        if (!force) {
			if (this._srcBlendFactor === cc.macro.BlendFactor.SRC_ALPHA && 
				this._dstBlendFactor === cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA) {
                return;
            }
        }
        
        let materials = this.getMaterials();
        for (let i = 0; i < materials.length; i++) {
            let material = materials[i];
            this._updateMaterialBlendFunc(material);
        }
    }

    _updateMaterialBlendFunc (material) {
        material.setBlend(
            true,
            gfx.BLEND_FUNC_ADD,
            this._srcBlendFactor, this._dstBlendFactor,
            gfx.BLEND_FUNC_ADD,
            this._srcBlendFactor, this._dstBlendFactor
        );
    }

}

}
